Danish updates from David Munch
[adiumx.git] / Plugins / Purple Service / ESPurpleJabberAccount.m
bloba96bd9d7e6c9d65cb272d915f9e3ec2dfc8aa0e9
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 #import "ESPurpleJabberAccount.h"
18 #import <AdiumLibpurple/SLPurpleCocoaAdapter.h>
19 #import <Adium/AIAccountControllerProtocol.h>
20 #import <Adium/AIInterfaceControllerProtocol.h>
21 #import <Adium/AIStatusControllerProtocol.h>
22 #import <Adium/AIContactControllerProtocol.h>
23 #import <Adium/AIChat.h>
24 #import <Adium/AIHTMLDecoder.h>
25 #import <Adium/AIListContact.h>
26 #import <Adium/AIStatus.h>
27 #import <Adium/ESFileTransfer.h>
28 #import <Adium/ESTextAndButtonsWindowController.h>
29 #import <Adium/AIService.h>
30 #import <AIUtilities/AIApplicationAdditions.h>
31 #import <AIUtilities/AIAttributedStringAdditions.h>
32 #import <AIUtilities/AIStringAdditions.h>
33 #include <libpurple/presence.h>
34 #include <libpurple/si.h>
35 #include <SystemConfiguration/SystemConfiguration.h>
36 #import "AMXMLConsoleController.h"
37 #import "AMPurpleJabberServiceDiscoveryBrowsing.h"
38 #import "ESPurpleJabberAccountViewController.h"
39 #import "AMPurpleJabberAdHocServer.h"
40 #import "AMPurpleJabberAdHocPing.h"
42 #ifdef HAVE_CDSA
43 #import "AIPurpleCertificateViewer.h"
44 #endif
46 #define DEFAULT_JABBER_HOST @"@jabber.org"
48 @interface ESPurpleJabberAccount (PRIVATE)
49 - (BOOL)enableXMLConsole;
50 @end
52 @implementation ESPurpleJabberAccount
54 /*!
55  * @brief The UID will be changed. The account has a chance to perform modifications
56  *
57  * Upgrade old Jabber accounts stored with the host in a separate key to have the right UID, in the form
58  * name@server.org
59  *
60  * Append @jabber.org to a proposed UID which has no domain name and does not need to be updated.
61  *
62  * @param proposedUID The proposed, pre-filtered UID (filtered means it has no characters invalid for this servce)
63  * @result The UID to use; the default implementation just returns proposedUID.
64  */
65 - (NSString *)accountWillSetUID:(NSString *)proposedUID
67         proposedUID = [proposedUID lowercaseString];
68         NSString        *correctUID;
69         
70         if ((proposedUID && ([proposedUID length] > 0)) && 
71            ([proposedUID rangeOfString:@"@"].location == NSNotFound)) {
72                 
73                 NSString        *host;
74                 //Upgrade code: grab a previously specified Jabber host
75                 if ((host = [self preferenceForKey:@"Jabber:Host" group:GROUP_ACCOUNT_STATUS ignoreInheritedValues:YES])) {
76                         //Determine our new, full UID
77                         correctUID = [NSString stringWithFormat:@"%@@%@",proposedUID, host];
79                         //Clear the preference and then set the UID so we don't perform this upgrade again
80                         [self setPreference:nil forKey:@"Jabber:Host" group:GROUP_ACCOUNT_STATUS];
81                         [self setPreference:correctUID forKey:@"FormattedUID" group:GROUP_ACCOUNT_STATUS];
83                 } else {
84                         //Append [self serverSuffix] (e.g. @jabber.org) to a Jabber account with no server
85                         correctUID = [proposedUID stringByAppendingString:[self serverSuffix]];
86                 }
87         } else {
88                 correctUID = proposedUID;
89         }
91         return correctUID;
94 - (const char*)protocolPlugin
96    return "prpl-jabber";
99 - (void)dealloc
101         [xmlConsoleController close];
102         [xmlConsoleController release];
104         [super dealloc];
107 - (NSSet *)supportedPropertyKeys
109         static NSMutableSet *supportedPropertyKeys = nil;
110         
111         if (!supportedPropertyKeys) {
112                 supportedPropertyKeys = [[NSMutableSet alloc] initWithObjects:
113                         @"AvailableMessage",
114                         @"Invisible",
115                         nil];
116                 [supportedPropertyKeys unionSet:[super supportedPropertyKeys]];
117         }
118         
119         return supportedPropertyKeys;
122 - (void)configurePurpleAccount
124         [super configurePurpleAccount];
125         
126         NSString        *connectServer;
127         BOOL            forceOldSSL, allowPlaintext, requireTLS;
129         purple_account_set_username(account, [self purpleAccountName]);
131         //'Connect via' server (nil by default)
132         connectServer = [self preferenceForKey:KEY_JABBER_CONNECT_SERVER group:GROUP_ACCOUNT_STATUS];
133         //XXX - As of libpurple 2.0.0, 'localhost' doesn't work properly by 127.0.0.1 does. Hack!
134         if (connectServer && [connectServer isEqualToString:@"localhost"])
135                 connectServer = @"127.0.0.1";
136         
137         purple_account_set_string(account, "connect_server", (connectServer ?
138                                                                                                                 [connectServer UTF8String] :
139                                                                                                                 ""));
140         
141         //Force old SSL usage? (off by default)
142         forceOldSSL = [[self preferenceForKey:KEY_JABBER_FORCE_OLD_SSL group:GROUP_ACCOUNT_STATUS] boolValue];
143         purple_account_set_bool(account, "old_ssl", forceOldSSL);
145         //Require SSL or TLS? (off by default)
146         requireTLS = [[self preferenceForKey:KEY_JABBER_REQUIRE_TLS group:GROUP_ACCOUNT_STATUS] boolValue];
147         purple_account_set_bool(account, "require_tls", requireTLS);
149         //Allow plaintext authorization over an unencrypted connection? Purple will prompt if this is NO and is needed.
150         allowPlaintext = [[self preferenceForKey:KEY_JABBER_ALLOW_PLAINTEXT group:GROUP_ACCOUNT_STATUS] boolValue];
151         purple_account_set_bool(account, "auth_plain_in_clear", allowPlaintext);
152         
153         /* Mac OS X 10.4's cyrus-sasl gives us problems.  Is it a bug in the installed library, a bug in its compilation, or a bug
154          * in our linkage against it? I don't know. In any case, work around it as much as possible by utilizing libpurple's own implementation
155          * of PLAIN and DIGEST-MD5 on 10.4. We can safely use cyrus-sasl in all its authenticating awesomeness for all methods on 10.5. -evands
156          *
157          * This preference is added via the "libpurple_jabber_avoid_sasl_option_hack.diff" patch we apply during the build process.
158          */
159         purple_prefs_set_bool("/plugins/prpl/jabber/avoid_sasl_for_plain_and_digest_md5_auth", [NSApp isTiger]);
162 - (NSString *)serverSuffix
164         NSString *host = [self preferenceForKey:KEY_JABBER_CONNECT_SERVER group:GROUP_ACCOUNT_STATUS];
165         
166         return (host ? host : DEFAULT_JABBER_HOST);
169 /*!     @brief  Obtain the resource name for this Jabber account.
171  *      This could be extended in the future to perform keyword substitution (e.g. s/%computerName%/CSCopyMachineName()/).
173  *      @return The resource name for the account.
174  */
175 - (NSString *)resourceName
177     NSString *resource = [self preferenceForKey:KEY_JABBER_RESOURCE group:GROUP_ACCOUNT_STATUS];
178     
179     if(resource == nil || [resource length] == 0)
180         resource = [(NSString*)SCDynamicStoreCopyLocalHostName(NULL) autorelease];
181     
182         return resource;
185 - (const char *)purpleAccountName
187         NSString        *userNameWithHost = nil, *completeUserName = nil;
188         BOOL            serverAppendedToUID;
189         
190         /*
191          * Purple stores the username in the format username@server/resource.  We need to pass it a username in this format
192          *
193          * The user should put the username in username@server format, which is common for Jabber. If the user does
194          * not specify the server, use jabber.org.
195          */
196         
197         serverAppendedToUID = ([UID rangeOfString:@"@"].location != NSNotFound);
198         
199         if (serverAppendedToUID) {
200                 userNameWithHost = UID;
201         } else {
202                 userNameWithHost = [UID stringByAppendingString:[self serverSuffix]];
203         }
205         completeUserName = [NSString stringWithFormat:@"%@/%@" ,userNameWithHost, [self resourceName]];
207         return [completeUserName UTF8String];
211  * @brief Connect Host
213  * Convenience method for retrieving the connect host for this account
215  * Rather than having a separate server field, Jabber uses the servername after the user name.
216  * username@server.org
218  * The connect server, stored in KEY_JABBER_CONNECT_SERVER, overrides this to provide the connect host. It will
219  * not be set in most cases.
220  */
221 - (NSString *)host
223         NSString        *host;
224         
225         if (!(host = [self preferenceForKey:KEY_JABBER_CONNECT_SERVER group:GROUP_ACCOUNT_STATUS])) {
226                 int location = [UID rangeOfString:@"@"].location;
228                 if ((location != NSNotFound) && (location + 1 < [UID length])) {
229                         host = [UID substringFromIndex:(location + 1)];
231                 } else {
232                         host = [self serverSuffix];
233                 }
234         }
235         
236         return host;
240  * @brief Should set aliases serverside?
242  * Jabber supports serverside aliases.
243  */
244 - (BOOL)shouldSetAliasesServerside
246         return YES;
249 - (AIListContact *)contactWithUID:(NSString *)sourceUID
251         AIListContact   *contact;
252         
253         contact = [[adium contactController] existingContactWithService:service
254                                                                                                                         account:self
255                                                                                                                                 UID:sourceUID];
256         if (!contact) {         
257                 contact = [[adium contactController] contactWithService:[self _serviceForUID:sourceUID]
258                                                                                                                 account:self
259                                                                                                                         UID:sourceUID];
260         }
261         
262         return contact;
265 - (AIService *)_serviceForUID:(NSString *)contactUID
267         AIService       *contactService;
268         NSString        *contactServiceID = nil;
270         if ([contactUID hasSuffix:@"@gmail.com"] ||
271                 [contactUID hasSuffix:@"@googlemail.com"]) {
272                 contactServiceID = @"libpurple-jabber-gtalk";
274         } else if([contactUID hasSuffix:@"@livejournal.com"]){
275                 contactServiceID = @"libpurple-jabber-livejournal";
276                 
277         } else {
278                 contactServiceID = @"libpurple-Jabber";
279         }
281         contactService = [[adium accountController] serviceWithUniqueID:contactServiceID];
282         
283         return contactService;
286 - (id)authorizationRequestWithDict:(NSDictionary*)dict {
287         id handle;
288         switch([[self preferenceForKey:KEY_JABBER_SUBSCRIPTION_BEHAVIOR group:GROUP_ACCOUNT_STATUS] intValue]) {
289                 case 2: // always accept + add
290                         // add
291                         {
292                                 NSString *groupname = [self preferenceForKey:KEY_JABBER_SUBSCRIPTION_GROUP group:GROUP_ACCOUNT_STATUS];
293                                 if([groupname length] > 0) {
294                                         AIListContact *contact = [[adium contactController] contactWithService:[self service] account:self UID:[dict objectForKey:@"Remote Name"]];
295                                         AIListGroup *group = [[adium contactController] groupWithUID:groupname];
296                                         [[adium contactController] addContacts:[NSArray arrayWithObject:contact] toGroup:group];
297                                 }
298                         }
299                         // fallthrough
300                 case 1: // always accept
301                         [[self purpleThread] doAuthRequestCbValue:[[[dict objectForKey:@"authorizeCB"] retain] autorelease] withUserDataValue:[[[dict objectForKey:@"userData"] retain] autorelease]];
302                         handle = [[NSObject alloc] init];
303                         break;
304                 case 3: // always deny
305                         [[self purpleThread] doAuthRequestCbValue:[[[dict objectForKey:@"denyCB"] retain] autorelease] withUserDataValue:[[[dict objectForKey:@"userData"] retain] autorelease]];
306                         handle = [[NSObject alloc] init];
307                         break;
308                 default: // ask (should be 0)
309                         return [super authorizationRequestWithDict:dict];
310         }
311         
312         // we can't execute this immediately, since libpurple doesn't know about the handle yet
313         [self performSelector:@selector(closeAuthRequest:) withObject:handle afterDelay:0.0];
314         return handle;
317 - (void)closeAuthRequest:(id)handle {
318         purple_account_request_close(handle);
321 - (void)purpleAccountRegistered:(BOOL)success
323         if(success && [[self service] accountViewController]) {
324                 const char *usernamestr = account->username;
325                 NSString *username;
326                 if (usernamestr) {
327                         NSString *userWithResource = [NSString stringWithUTF8String:usernamestr];
328                         NSRange slashrange = [userWithResource rangeOfString:@"/"];
329                         if(slashrange.location != NSNotFound)
330                                 username = [userWithResource substringToIndex:slashrange.location];
331                         else
332                                 username = userWithResource;
333                 } else
334                         username = (id)[NSNull null];
336                 NSString *pw = (account->password ? [NSString stringWithUTF8String:account->password] : [NSNull null]);
337                 
338                 [[adium notificationCenter] postNotificationName:AIAccountUsernameAndPasswordRegisteredNotification
339                                                                                                   object:self
340                                                                                                 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
341                                                                                                         username, @"username",
342                                                                                                         pw, @"password",
343                                                                                                         nil]];
344         }
347 #pragma mark Contacts
348 - (void)updateSignon:(AIListContact *)theContact withData:(void *)data
350         [super updateSignon:theContact withData:data];
351         
352         //We only get user icons in Jabber when we request info. Do that now!
353         [self delayedUpdateContactStatus:theContact];
356 #pragma mark Status
358 - (NSString *)encodedAttributedString:(NSAttributedString *)inAttributedString forListObject:(AIListObject *)inListObject
360         static AIHTMLDecoder *jabberHtmlEncoder = nil;
361         if (!jabberHtmlEncoder) {
362                 jabberHtmlEncoder = [[AIHTMLDecoder alloc] init];
363                 [jabberHtmlEncoder setIncludesHeaders:NO];
364                 [jabberHtmlEncoder setIncludesFontTags:YES];
365                 [jabberHtmlEncoder setClosesFontTags:YES];
366                 [jabberHtmlEncoder setIncludesStyleTags:YES];
367                 [jabberHtmlEncoder setIncludesColorTags:YES];
368                 [jabberHtmlEncoder setEncodesNonASCII:NO];
369                 [jabberHtmlEncoder setPreservesAllSpaces:NO];
370                 [jabberHtmlEncoder setUsesAttachmentTextEquivalents:YES];
371         }
372         
373         return [jabberHtmlEncoder encodeHTML:inAttributedString imagesPath:nil];
376 - (NSString *)_UIDForAddingObject:(AIListContact *)object
378         NSString        *objectUID = [object UID];
379         NSString        *properUID;
380         
381         if ([objectUID rangeOfString:@"@"].location != NSNotFound) {
382                 properUID = objectUID;
383         } else {
384                 properUID = [NSString stringWithFormat:@"%@@%@",objectUID,[self host]];
385         }
386         
387         return [properUID lowercaseString];
390 - (NSString *)unknownGroupName {
391     return (AILocalizedString(@"Roster","Roster - the Jabber default group"));
394 - (NSString *)connectionStringForStep:(int)step
396         switch (step) {
397                 case 0:
398                         return AILocalizedString(@"Connecting",nil);
399                         break;
400                 case 1:
401                         return AILocalizedString(@"Initializing Stream",nil);
402                         break;
403                 case 2:
404                         return AILocalizedString(@"Reading data",nil);
405                         break;                  
406                 case 3:
407                         return AILocalizedString(@"Authenticating",nil);
408                         break;
409                 case 5:
410                         return AILocalizedString(@"Initializing Stream",nil);
411                         break;
412                 case 6:
413                         return AILocalizedString(@"Authenticating",nil);
414                         break;
415         }
416         return nil;
419 - (AIReconnectDelayType)shouldAttemptReconnectAfterDisconnectionError:(NSString **)disconnectionError
421         AIReconnectDelayType shouldAttemptReconnect = [super shouldAttemptReconnectAfterDisconnectionError:disconnectionError];
423         if (([self lastDisconnectionReason] == PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR) &&
424                 ([self shouldVerifyCertificates])) {
425                 shouldAttemptReconnect = AIReconnectNever;
426         }
428         return shouldAttemptReconnect;
431 - (void)disconnectFromDroppedNetworkConnection
433         /* Before we disconnect from a dropped network connection, set gc->disconnect_timeout to a non-0 value.
434          * This will let the prpl know that we are disconnecting with no backing ssl connection and that therefore
435          * the ssl connection is has should not be messaged in the process of disconnecting.
436          */
437         PurpleConnection *gc = purple_account_get_connection(account);
438         if (PURPLE_CONNECTION_IS_VALID(gc) &&
439                 !gc->disconnect_timeout) {
440                 gc->disconnect_timeout = -1;
441                 AILog(@"%@: Disconnecting from a dropped network connection", self);
442         }
444         [super disconnectFromDroppedNetworkConnection];
447 - (BOOL)shouldIncludeNowPlayingInformationInAllStatuses
449         return [[self preferenceForKey:KEY_BROADCAST_MUSIC_INFO group:GROUP_ACCOUNT_STATUS] boolValue];
452 #pragma mark File transfer
453 - (BOOL)canSendFolders
455         return NO;
458 - (void)beginSendOfFileTransfer:(ESFileTransfer *)fileTransfer
460         [super _beginSendOfFileTransfer:fileTransfer];
463 - (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
465     [super acceptFileTransferRequest:fileTransfer];    
468 - (void)rejectFileReceiveRequest:(ESFileTransfer *)fileTransfer
470     [super rejectFileReceiveRequest:fileTransfer];    
473 - (void)cancelFileTransfer:(ESFileTransfer *)fileTransfer
475         [super cancelFileTransfer:fileTransfer];
478 #pragma mark Status Messages
479 - (NSAttributedString *)statusMessageForPurpleBuddy:(PurpleBuddy *)b
481         NSAttributedString  *statusMessage = nil;
483         if (purple_account_is_connected(account)) {             
484                 char    *normalized = g_strdup(purple_normalize(b->account, b->name));
485                 JabberBuddy     *jb;
486                 
487                 if ((jb = jabber_buddy_find(account->gc->proto_data, normalized, FALSE))) {
488                         NSString        *statusMessageString = nil;
489                         const char      *msg = jabber_buddy_get_status_msg(jb);
490                         
491                         if (msg) {
492                                 //Get the custom jabber status message if one is set
493                                 statusMessageString = [NSString stringWithUTF8String:msg];
494                         }
495                         
496                         if (statusMessageString && [statusMessageString length]) {
497                                 statusMessage = [AIHTMLDecoder decodeHTML:statusMessageString];
498                         }
499                 }
500                 
501                 g_free(normalized);
502         }
503         
504         return statusMessage;
507 - (NSString *)statusNameForPurpleBuddy:(PurpleBuddy *)buddy
509         NSString                *statusName = nil;
510         PurplePresence  *presence = purple_buddy_get_presence(buddy);
511         PurpleStatus            *status = purple_presence_get_active_status(presence);
512         const char              *purpleStatusID = purple_status_get_id(status);
513         
514         if (!purpleStatusID) return nil;
516         if (!strcmp(purpleStatusID, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_CHAT))) {
517                 statusName = STATUS_NAME_FREE_FOR_CHAT;
518                 
519         } else if (!strcmp(purpleStatusID, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_XA))) {
520                 statusName = STATUS_NAME_EXTENDED_AWAY;
521                 
522         } else if (!strcmp(purpleStatusID, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_DND))) {
523                 statusName = STATUS_NAME_DND;
524                 
525         }
526         
527         return statusName;
531  * @brief Jabber status messages are plaintext
532  */
533 - (NSString *)encodedAttributedString:(NSAttributedString *)inAttributedString forStatusState:(AIStatus *)statusState
535         return [[inAttributedString attributedStringByConvertingLinksToStrings] string];
538 #pragma mark Menu items
539 - (NSString *)titleForContactMenuLabel:(const char *)label forContact:(AIListContact *)inContact
541         if (strcmp(label, "Un-hide From") == 0) {
542                 return [NSString stringWithFormat:AILocalizedString(@"Un-hide From %@",nil),[inContact formattedUID]];
544         } else if (strcmp(label, "Temporarily Hide From") == 0) {
545                 return [NSString stringWithFormat:AILocalizedString(@"Temporarily Hide From %@",nil),[inContact formattedUID]];
547         } else if (strcmp(label, "Unsubscribe") == 0) {
548                 return [NSString stringWithFormat:AILocalizedString(@"Unsubscribe %@",nil),[inContact formattedUID]];
550         } else if (strcmp(label, "(Re-)Request authorization") == 0) {
551                 return [NSString stringWithFormat:AILocalizedString(@"Re-request Authorization from %@",nil),[inContact formattedUID]];
553         } else if (strcmp(label,  "Cancel Presence Notification") == 0) {
554                 return [NSString stringWithFormat:AILocalizedString(@"Cancel Presence Notification to %@",nil),[inContact formattedUID]];       
555         }
556         
557         return [super titleForContactMenuLabel:label forContact:inContact];
560 - (NSString *)titleForAccountActionMenuLabel:(const char *)label
561 {       
562         if (strcmp(label, "Set User Info...") == 0) {
563                 return [AILocalizedString(@"Set User Info", nil) stringByAppendingEllipsis];
564                 
565         } else  if (strcmp(label, "Search for Users...") == 0) {
566                 return [AILocalizedString(@"Search for Users", nil) stringByAppendingEllipsis];
567                 
568         } else  if (strcmp(label, "Set Mood...") == 0) {
569                 return [AILocalizedString(@"Set Mood", nil) stringByAppendingEllipsis];
570                 
571         } else  if (strcmp(label, "Set Nickname...") == 0) {
572                 return [AILocalizedString(@"Set Nickname", nil) stringByAppendingEllipsis];
573         } 
574         
575         return [super titleForAccountActionMenuLabel:label];
578 #pragma mark Multiuser chat
580 //Multiuser chats come in with just the contact's name as contactName, but we want to actually do it right.
581 - (NSString *)uidForContactWithUID:(NSString *)inUID inChat:(AIChat *)chat
583         return [NSString stringWithFormat:@"%@/%@",[chat name],inUID];
586 #pragma mark Status
588  * @brief Return the purple status type to be used for a status
590  * Most subclasses should override this method; these generic values may be appropriate for others.
592  * Active services provided nonlocalized status names.  An AIStatus is passed to this method along with a pointer
593  * to the status message.  This method should handle any status whose statusNname this service set as well as any statusName
594  * defined in  AIStatusController.h (which will correspond to the services handled by Adium by default).
595  * It should also handle a status name not specified in either of these places with a sane default, most likely by loooking at
596  * [statusState statusType] for a general idea of the status's type.
598  * @param statusState The status for which to find the purple status ID
599  * @param arguments Prpl-specific arguments which will be passed with the state. Message is handled automatically.
601  * @result The purple status ID
602  */
603 - (const char *)purpleStatusIDForStatus:(AIStatus *)statusState
604                                                         arguments:(NSMutableDictionary *)arguments
606         const char              *statusID = NULL;
607         NSString                *statusName = [statusState statusName];
608         NSString                *statusMessageString = [statusState statusMessageString];
609         NSNumber                *priority = nil;
610         
611         if (!statusMessageString) statusMessageString = @"";
613         switch ([statusState statusType]) {
614                 case AIAvailableStatusType:
615                 {
616                         if (([statusName isEqualToString:STATUS_NAME_FREE_FOR_CHAT]) ||
617                            ([statusMessageString caseInsensitiveCompare:[[adium statusController] localizedDescriptionForCoreStatusName:STATUS_NAME_FREE_FOR_CHAT]] == NSOrderedSame))
618                                 statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_CHAT);
619                         priority = [self preferenceForKey:KEY_JABBER_PRIORITY_AVAILABLE group:GROUP_ACCOUNT_STATUS];
620                         break;
621                 }
622                         
623                 case AIAwayStatusType:
624                 {
625                         if (([statusName isEqualToString:STATUS_NAME_DND]) ||
626                            ([statusMessageString caseInsensitiveCompare:[[adium statusController] localizedDescriptionForCoreStatusName:STATUS_NAME_DND]] == NSOrderedSame))
627                                 statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_DND);
628                         else if (([statusName isEqualToString:STATUS_NAME_EXTENDED_AWAY]) ||
629                                          ([statusMessageString caseInsensitiveCompare:[[adium statusController] localizedDescriptionForCoreStatusName:STATUS_NAME_EXTENDED_AWAY]] == NSOrderedSame))
630                                 statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_XA);
631                         priority = [self preferenceForKey:KEY_JABBER_PRIORITY_AWAY group:GROUP_ACCOUNT_STATUS];
632                         break;
633                 }
634                         
635                 case AIInvisibleStatusType:
636                         AILog(@"Warning: Invisibility is not yet supported in libpurple 2.0.0 jabber");
637                         priority = [self preferenceForKey:KEY_JABBER_PRIORITY_AWAY group:GROUP_ACCOUNT_STATUS];
638                         statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_AWAY);
639 //                      statusID = "Invisible";
640                         break;
641                         
642                 case AIOfflineStatusType:
643                         break;
644         }
646         //Set our priority, which is actually set along with the status...Default is 0.
647         [arguments setObject:(priority ? priority : [NSNumber numberWithInt:0])
648                                   forKey:@"priority"];
649         
650         //We could potentially set buzz on a per-status basis. We have no UI for this, however.
651         [arguments setObject:[NSNumber numberWithBool:YES] forKey:@"buzz"];
653         //If we didn't get a purple status ID, request one from super
654         if (statusID == NULL) statusID = [super purpleStatusIDForStatus:statusState arguments:arguments];
655         
656         return statusID;
659 #pragma mark Gateway Tracking
661 - (void)updateContact:(AIListContact *)theContact toGroupName:(NSString *)groupName contactName:(NSString *)contactName {
662         NSRange atsign = [[theContact UID] rangeOfString:@"@"];
663         if(atsign.location != NSNotFound)
664                 [super updateContact:theContact toGroupName:groupName contactName:contactName];
665         else {
666                 NSEnumerator *e = [gateways objectEnumerator];
667                 NSDictionary *gatewaydict;
668                 // avoid duplicates!
669                 while((gatewaydict = [e nextObject])) {
670                         if([[[gatewaydict objectForKey:@"contact"] UID] isEqualToString:[theContact UID]]) {
671                                 [gateways removeObjectIdenticalTo:gatewaydict];
672                                 break;
673                         }
674                 }
676                 [gateways addObject:[NSDictionary dictionaryWithObjectsAndKeys:
677                                                          theContact, @"contact",
678                                                          groupName, @"remoteGroup",
679                                                          nil]];
680         }
683 - (void)removeContact:(AIListContact *)theContact {
684         NSRange atsign = [[theContact UID] rangeOfString:@"@"];
685         if(atsign.location != NSNotFound)
686                 [super removeContact:theContact];
687         else {
688                 NSEnumerator *e = [gateways objectEnumerator];
689                 NSDictionary *gatewaydict;
690                 while((gatewaydict = [e nextObject])) {
691                         if([[[gatewaydict objectForKey:@"contact"] UID] isEqualToString:[theContact UID]]) {
692                                 [[self purpleThread] removeUID:[theContact UID] onAccount:self fromGroup:[gatewaydict objectForKey:@"remoteGroup"]];
693                                 
694                                 [gateways removeObjectIdenticalTo:gatewaydict];
695                                 break;
696                         }
697                 }
698         }
701 #pragma mark XML Console, Tooltip, AdHoc Server Integration and Gateway Integration
704 * @brief Returns whether or not this account is connected via an encrypted connection.
705  */
706 - (BOOL)encrypted
708         return ([self online] && [self secureConnection]);
711 - (void)didConnect {
712         gateways = [[NSMutableArray alloc] init];
714         if(adhocServer)
715                 [adhocServer release];
716         adhocServer = [[AMPurpleJabberAdHocServer alloc] initWithAccount:self];
717         [adhocServer addCommand:@"ping" delegate:[AMPurpleJabberAdHocPing class] name:@"Ping"];
718         
719     [super didConnect];
720         
721         if ([self enableXMLConsole]) {
722                 if (!xmlConsoleController) xmlConsoleController = [[AMXMLConsoleController alloc] init];
723                 [xmlConsoleController setPurpleConnection:purple_account_get_connection(account)];
724         }
726         discoveryBrowserController = [[AMPurpleJabberServiceDiscoveryBrowsing alloc] initWithAccount:self
727                                                                                                                                                                 purpleConnection:purple_account_get_connection(account)];
730 - (void)didDisconnect {
731         [xmlConsoleController setPurpleConnection:NULL];
732         
733         [discoveryBrowserController release]; discoveryBrowserController = nil;
734         [adhocServer release]; adhocServer = nil;
736         [super didDisconnect];
738         [gateways release]; gateways = nil;
741 - (IBAction)showXMLConsole:(id)sender {
742     if(xmlConsoleController)
743         [xmlConsoleController showWindow:sender];
744     else
745         NSBeep();
748 - (BOOL)enableXMLConsole
750         BOOL enableConsole;
751 #ifdef DEBUG_BUILD
752         //Always enable the XML console for debug builds
753         enableConsole = YES;
754 #else
755         //For non-debug builds, only enable it if the preference is set
756         enableConsole = [[NSUserDefaults standardUserDefaults] boolForKey:@"AMXMPPShowAdvanced"];
757 #endif
758         
759         return enableConsole;
762 - (IBAction)showDiscoveryBrowser:(id)sender {
763         [discoveryBrowserController browse:sender];
766 - (PurpleSslConnection*)secureConnection {
767         // this is really ugly
768         if([self purpleAccount]->gc && [self purpleAccount]->gc->proto_data)
769                 return ((JabberStream*)[self purpleAccount]->gc->proto_data)->gsc;
770         return NULL;
773 - (void)setShouldVerifyCertificates:(BOOL)yesOrNo {
774         [self setPreference:[NSNumber numberWithBool:yesOrNo] forKey:KEY_JABBER_VERIFY_CERTS group:GROUP_ACCOUNT_STATUS];
777 - (BOOL)shouldVerifyCertificates {
778         return ([self preferenceForKey:KEY_JABBER_VERIFY_CERTS group:GROUP_ACCOUNT_STATUS]==nil) || [[self preferenceForKey:KEY_JABBER_VERIFY_CERTS group:GROUP_ACCOUNT_STATUS] boolValue];
781 #ifdef HAVE_CDSA
782 - (IBAction)showServerCertificate:(id)sender {
783         CFArrayRef certificates = [[self purpleThread] copyServerCertificates:[self secureConnection]];
784         
785         [AIPurpleCertificateViewer displayCertificateChain:certificates forAccount:self];
786         CFRelease(certificates);
788 #endif
790 - (NSArray *)accountActionMenuItems {
791         AILog(@"Getting accountActionMenuItems for %@",self);
792         NSMutableArray *menu = [[NSMutableArray alloc] init];
793         
794         if([gateways count] > 0) {
795                 NSEnumerator *e = [gateways objectEnumerator];
796                 NSDictionary *gatewaydict;
797                 while((gatewaydict = [e nextObject])) {
798                         AIListContact *gateway = [gatewaydict objectForKey:@"contact"];
799                         NSMenuItem *mitem = [[NSMenuItem alloc] initWithTitle:[gateway UID] action:@selector(registerGateway:) keyEquivalent:@""];
800                         NSMenu *submenu = [[NSMenu alloc] initWithTitle:[gateway UID]];
801                         
802                         NSArray *menuitemarray = [self menuItemsForContact:gateway];
803                         NSEnumerator *e2 = [menuitemarray objectEnumerator];
804                         NSMenuItem *m2item;
805                         while((m2item = [e2 nextObject]))
806                                 [submenu addItem:m2item];
807                         
808                         if([submenu numberOfItems] > 0)
809                                 [submenu addItem:[NSMenuItem separatorItem]];
811                         NSMenuItem *removeItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Remove gateway","gateway menu item") action:@selector(removeGateway:) keyEquivalent:@""];
812                         [removeItem setTarget:self];
813                         [removeItem setRepresentedObject:gateway];
814                         [submenu addItem:removeItem];
815                         [removeItem release];
816                         
817                         [mitem setSubmenu:submenu];
818                         [submenu release];
819                         [mitem setRepresentedObject:gateway];
820                         [mitem setImage:[gateway displayArrayObjectForKey:@"Tab Status Icon"]];
821                         [mitem setTarget:self];
822                         [menu addObject:mitem];
823                         [mitem release];
824                 }
825         [menu addObject:[NSMenuItem separatorItem]];
826         }
827         
828     NSArray *supermenu = [super accountActionMenuItems];
829     if(supermenu) {
830                 [menu addObjectsFromArray:supermenu];
831         [menu addObject:[NSMenuItem separatorItem]];
832         }
834         if ([self enableXMLConsole]) {
835                 NSMenuItem *xmlConsoleMenuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"XML Console",nil)
836                                                                                                                                         action:@selector(showXMLConsole:) 
837                                                                                                                          keyEquivalent:@""];
838                 [xmlConsoleMenuItem setTarget:self];
839                 [menu addObject:xmlConsoleMenuItem];
840                 [xmlConsoleMenuItem release];
841         }
843         NSMenuItem *discoveryBrowserMenuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Discovery Browser",nil)
844                                                                                                                                           action:@selector(showDiscoveryBrowser:) 
845                                                                                                                            keyEquivalent:@""];
846     [discoveryBrowserMenuItem setTarget:self];
847     [menu addObject:discoveryBrowserMenuItem];
848     [discoveryBrowserMenuItem release];
849         
850 #ifdef HAVE_CDSA
851         if([self encrypted]) {
852                 NSMenuItem *showCertificateMenuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Show Server Certificate",nil)
853                                                                                                                                                   action:@selector(showServerCertificate:) 
854                                                                                                                                    keyEquivalent:@""];
855                 [showCertificateMenuItem setTarget:self];
856                 [menu addObject:showCertificateMenuItem];
857                 [showCertificateMenuItem release];
858         }
859 #endif
860         
861     return [menu autorelease];
864 - (void)registerGateway:(NSMenuItem*)mitem {
865         if(mitem && [mitem representedObject])
866                 jabber_register_gateway((JabberStream*)[self purpleAccount]->gc->proto_data, [[[mitem representedObject] UID] UTF8String]);
867         else
868                 NSBeep();
871 - (void)removeGateway:(NSMenuItem*)mitem {
872         AIListContact *gateway = [mitem representedObject];
873         if(![gateway isKindOfClass:[AIListContact class]])
874                 return;
875         // since this is a potentially dangerous operation, get a confirmation from the user first
876         if([[NSAlert alertWithMessageText:AILocalizedString(@"Really remove gateway?",nil)
877                                          defaultButton:AILocalizedString(@"Remove","alert default button")
878                                    alternateButton:AILocalizedString(@"Cancel",nil)
879                                            otherButton:nil
880                                          informativeTextWithFormat:AILocalizedString(@"This operation would remove the gateway %@ itself and all contacts belonging to the gateway on your contact list. It cannot be undone.",nil), [gateway UID]] runModal] == NSAlertDefaultReturn) {
881                 // first, locate all contacts on the roster that belong to this gateway
882                 NSString *jid = [gateway UID];
883                 NSString *pattern = [@"@" stringByAppendingString:jid];
884                 NSMutableArray *gatewayContacts = [[NSMutableArray alloc] init];
885                 NSEnumerator *e = [[[adium contactController] allContactsOnAccount:self] objectEnumerator];
886                 AIListContact *contact;
887                 while((contact = [e nextObject])) {
888                         if([[contact UID] hasSuffix:pattern])
889                                 [gatewayContacts addObject:contact];
890                 }
891                 // now, remove them from the roster
892                 [self removeContacts:gatewayContacts];
893                 
894                 // finally, remove the gateway itself
895                 [self removeContact:gateway];
896         }
899 - (AMPurpleJabberAdHocServer*)adhocServer {
900         return adhocServer;
903 @end